// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/command_line.h"
#include "base/run_loop.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/render_view_observer.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/renderer/render_frame_impl.h"
#include "content/shell/browser/shell.h"

namespace content {

class CommitObserver : public RenderViewObserver {
public:
    CommitObserver(RenderView* render_view)
        : RenderViewObserver(render_view)
        , quit_closures_()
        , commit_count_(0)
    {
    }

    void DidCommitCompositorFrame() override
    {
        commit_count_++;
        for (base::Closure* closure : quit_closures_) {
            closure->Run();
        }
    }

    void QuitAfterCommit(int commit_number,
        scoped_refptr<MessageLoopRunner> runner)
    {
        if (commit_number >= commit_count_)
            runner->Quit();
    }

    void WaitForCommitNumber(int commit_number)
    {
        if (commit_number > commit_count_) {
            scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner;
            base::Closure quit_closure = base::Bind(&CommitObserver::QuitAfterCommit, base::Unretained(this),
                commit_number, runner);
            quit_closures_.insert(&quit_closure);
            runner->Run();
            quit_closures_.erase(&quit_closure);
        }
    }

    int GetCommitCount() { return commit_count_; }

private:
    // RenderViewObserver implementation.
    void OnDestruct() override { delete this; }

    std::set<base::Closure*> quit_closures_;
    int commit_count_;
};

class VisualStateTest : public ContentBrowserTest {
public:
    VisualStateTest()
        : callback_count_(0)
    {
    }

    void SetUpCommandLine(base::CommandLine* command_line) override
    {
        command_line->AppendSwitch(switches::kSingleProcess);
    }

    void WaitForCommit(CommitObserver* observer, int commit_number)
    {
        observer->WaitForCommitNumber(commit_number);
        EXPECT_EQ(commit_number, observer->GetCommitCount());
    }

    void AssertIsIdle()
    {
        ASSERT_TRUE(base::MessageLoop::current()->IsIdleForTesting());
    }

    void InvokeVisualStateCallback(bool result)
    {
        EXPECT_TRUE(result);
        callback_count_++;
    }

    int GetCallbackCount() { return callback_count_; }

private:
    int callback_count_;
};

// This test verifies that visual state callbacks do not deadlock. In other
// words, the visual state callback should be received even if there are no
// pending updates or commits.
// Disabled due to cross-platform flakes; http://crbug.com/462580.
IN_PROC_BROWSER_TEST_F(VisualStateTest, DISABLED_CallbackDoesNotDeadlock)
{
    // This test relies on the fact that loading "about:blank" only requires a
    // single commit. We first load "about:blank" and wait for this single
    // commit. At that point we know that the page has stabilized and no
    // further commits are expected. We then insert a visual state callback
    // and verify that this causes an additional commit in order to deliver
    // the callback.
    // Unfortunately, if loading "about:blank" changes and starts requiring
    // two commits then this test will prove nothing. We could detect this
    // with a high level of confidence if we used a timeout, but that's
    // discouraged (see https://codereview.chromium.org/939673002).
    NavigateToURL(shell(), GURL("about:blank"));
    CommitObserver observer(RenderView::FromRoutingID(
        shell()->web_contents()->GetRenderViewHost()->GetRoutingID()));

    // Wait for the commit corresponding to the load.

    PostTaskToInProcessRendererAndWait(base::Bind(
        &VisualStateTest::WaitForCommit, base::Unretained(this), &observer, 1));

    // Try our best to check that there are no pending updates or commits.
    PostTaskToInProcessRendererAndWait(
        base::Bind(&VisualStateTest::AssertIsIdle, base::Unretained(this)));

    // Insert a visual state callback.
    shell()->web_contents()->GetMainFrame()->InsertVisualStateCallback(base::Bind(
        &VisualStateTest::InvokeVisualStateCallback, base::Unretained(this)));

    // Verify that the callback is invoked and a new commit completed.
    PostTaskToInProcessRendererAndWait(base::Bind(
        &VisualStateTest::WaitForCommit, base::Unretained(this), &observer, 2));
    EXPECT_EQ(1, GetCallbackCount());
}

} // namespace content
